// This is the main DLL file.

#include "stdafx.h"
#include "Device.h"
#include "DeviceInfo.h"
#include "Utility.h"

using namespace System::Runtime::InteropServices;

Microsoft::Robotics::ManagedBulkUsb::Device::Device(String^ deviceId)
{
    if (String::IsNullOrWhiteSpace(deviceId))
    {
        throw gcnew System::ArgumentNullException("deviceId");
    }

    _deviceId = deviceId;
    _psrwLock = new SRWLOCK;
    InitializeSRWLock(_psrwLock);
}

Microsoft::Robotics::ManagedBulkUsb::Device::~Device()
{
    Disconnect();
}

Microsoft::Robotics::ManagedBulkUsb::Device::!Device()
{
    Disconnect();
    delete _psrwLock;
}

void Microsoft::Robotics::ManagedBulkUsb::Device::Connect()
{
    exclusive_lock_t wlock(_psrwLock);

    if (_pdi)
    {
        throw gcnew InvalidOperationException("Already connected");
    }

    pin_ptr<const wchar_t> pszDeviceId = PtrToStringChars(_deviceId);
    GUID deviceInterface = {0};
    if (RPC_S_OK != UuidFromString((RPC_WSTR)pszDeviceId, &deviceInterface))
    {
        throw gcnew ArgumentException("deviceId");
    }

    DeviceInfo_t *pdi = new DeviceInfo_t;
    if (!pdi->Initialize(&deviceInterface))
    {
        delete pdi;
        throw gcnew InvalidOperationException("Initialization failed");
    }

    _pdi = pdi;
    _pdi->AddRef();
}

void Microsoft::Robotics::ManagedBulkUsb::Device::Disconnect()
{
    exclusive_lock_t wlock(_psrwLock);

    if (_pdi)
    {
        _pdi->Shutdown();
        _pdi->Release();
        _pdi = NULL;
    }
}

AsyncEventArgs^ Microsoft::Robotics::ManagedBulkUsb::Device::PrepareAsyncOperation(array<Byte>^ buffer)
{
    return gcnew DeviceAsyncEventArgs(this, buffer, this->_pdi);
}

bool Microsoft::Robotics::ManagedBulkUsb::Device::AsyncRead(AsyncEventArgs^ eventArgs)
{
    if (eventArgs == nullptr)
    {
        throw gcnew ArgumentNullException("eventArgs");
    }

    return IssueAsync(safe_cast<DeviceAsyncEventArgs^>(eventArgs), AsyncOperation::Read);
}

bool Microsoft::Robotics::ManagedBulkUsb::Device::AsyncWrite(AsyncEventArgs^ eventArgs)
{
    if (eventArgs == nullptr)
    {
        throw gcnew ArgumentNullException("eventArgs");
    }

    return IssueAsync(
        safe_cast<DeviceAsyncEventArgs^>(eventArgs),
        AsyncOperation::Write);
}

Int32 Microsoft::Robotics::ManagedBulkUsb::Device::IssueSync(array<Byte>^ buffer, Int32 offset, Int32 count, AsyncOperation opType)
{
    shared_lock_t rlock(_psrwLock);

    if (_pdi == 0)
    {
        throw gcnew InvalidOperationException("Not connected");
    }

    if (buffer == nullptr)
    {
        throw gcnew ArgumentNullException("buffer");
    }

    if (offset < 0 || offset >= buffer->Length)
    {
        throw gcnew ArgumentOutOfRangeException("offset");
    }

    if (count < 0)
    {
        throw gcnew ArgumentOutOfRangeException("count");
    }

    if (offset + count > buffer->Length)
    {
        throw gcnew ArgumentOutOfRangeException("offset + count");
    }

    PFNPIPEIO fnPipeOp = NULL;
    BYTE bPipe;
    switch (opType)
    {
    case AsyncOperation::Read:
        fnPipeOp = WinUsb_ReadPipe;
        bPipe = _pdi->bulkInPipe;
        break;
    case AsyncOperation::Write:
        fnPipeOp = WinUsb_WritePipe;
        bPipe = _pdi->bulkOutPipe;
        break;
    default:
        throw gcnew ArgumentOutOfRangeException("opType");
    }

    pin_ptr<UCHAR> rawPtr = &buffer[offset];
    ULONG cbTransferred = 0;
    BOOL fRet = fnPipeOp(
        _pdi->winUSBHandle,
        bPipe,
        rawPtr,
        count,
        &cbTransferred,
        NULL);
    if (FALSE == fRet)
    {
        const DWORD size = 256+1;
        WCHAR buffer[size];
        DWORD error = GetLastError();
        DWORD msgLen = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, buffer, size, NULL);
        throw gcnew System::ComponentModel::Win32Exception(error, gcnew System::String(buffer));
    }

    return (int)cbTransferred;
}

Boolean Microsoft::Robotics::ManagedBulkUsb::Device::IssueAsync(DeviceAsyncEventArgs^ eventArgs, AsyncOperation opType)
{
    shared_lock_t rlock(_psrwLock);

    if (_pdi == 0)
    {
        throw gcnew InvalidOperationException("Not connected");
    }

    PFNPIPEIO fnPipeOp = NULL;
    BYTE bPipe;
    switch (opType)
    {
    case AsyncOperation::Read:
        fnPipeOp = WinUsb_ReadPipe;
        bPipe = _pdi->bulkInPipe;
        break;
    case AsyncOperation::Write:
        fnPipeOp = WinUsb_WritePipe;
        bPipe = _pdi->bulkOutPipe;
        break;
    default:
        throw gcnew ArgumentOutOfRangeException("opType");
    }

    eventArgs->BytesTransferred = 0;
    eventArgs->LastError = nullptr;
    eventArgs->OperationType = opType;

    // Issue the async
    auto rawPtr = Marshal::UnsafeAddrOfPinnedArrayElement(
        eventArgs->Buffer, 
        eventArgs->Offset);

    BOOL fRet   = fnPipeOp(
        _pdi->winUSBHandle,
        bPipe,
        (PUCHAR)rawPtr.ToPointer(),
        eventArgs->Count,
        NULL,
        eventArgs->GetNativeOverlapped());


    return fRet ? true : false;
}

void Microsoft::Robotics::ManagedBulkUsb::Device::GetOverlappedResult(DeviceAsyncEventArgs^ eventArgs)
{
    DWORD cbTransferred = 0;

    if (eventArgs->pdi)
    {
        // Get the result of the async op
        auto fReturn = WinUsb_GetOverlappedResult(
                eventArgs->pdi->winUSBHandle,
                eventArgs->GetNativeOverlapped(),
                &cbTransferred,
                FALSE);


        // Update the op to signal completion.
        if (fReturn)
        {
            eventArgs->BytesTransferred = cbTransferred;
        }
        else
        {
            const DWORD size = 256+1;
            WCHAR buffer[size];
            DWORD error = GetLastError();
            DWORD msgLen = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, buffer, size, NULL);
            eventArgs->LastError = gcnew System::ComponentModel::Win32Exception(error, gcnew System::String(buffer));
        }
    }
}
